package com

import (
	"bufio"
	"bytes"
	"crypto/aes"
	"crypto/cipher"
	"crypto/md5"
	"crypto/sha256"
	"encoding/csv"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"hash/crc32"
	"image"
	"image/color"
	"image/draw"
	"image/jpeg"
	_ "image/png"
	"io"
	"io/ioutil"
	"math"
	"math/big"
	"net"
	"net/http"
	"net/url"
	"os"
	"os/user"
	"path/filepath"
	"pd/server/plugin/logger"
	"reflect"
	"regexp"
	"runtime"
	"strconv"
	"strings"
	"sync"
	"time"
	"unicode/utf8"

	"github.com/dgrijalva/jwt-go"
	"github.com/xuri/excelize/v2"

	"github.com/tealeg/xlsx"

	"github.com/bwmarrin/snowflake"
	"github.com/gin-gonic/gin"
	"github.com/imroc/req"
	"github.com/sirupsen/logrus"
)

const (
	formatTime        = "15:04:05"
	formatDate        = "2006-01-02"
	formatDateTime    = "2006-01-02 15:04:05"
	formatDateTimeUTC = "2006-01-02T15:04:05.000Z"
)

var DefaultTimeLoc = time.Local

func FileExists(fileName string) bool {
	if _, err := os.Stat(fileName); err == nil {
		return true
	}
	return false
}

func DirectoryExists(directory string) bool {
	_, err := os.Stat(directory)
	return !os.IsNotExist(err)
}

func Mkdir(directory string) error {
	err := os.MkdirAll(directory, os.ModePerm)
	return err
}

func TouchFile(fileName string) error {
	f, err := os.OpenFile(fileName, os.O_APPEND|os.O_CREATE, 0755)
	if err != nil {
		return err
	}
	return f.Close()
}

// StringContainsAll returns true if `s` contains all non empty given `substrings`
// The function returns `false` if no non-empty arguments are given.
func StringContainsAll(s string, substrings ...string) bool {
	nonEmptyStringsFound := false
	for _, substring := range substrings {
		if substring == "" {
			continue
		}
		if strings.Contains(s, substring) {
			nonEmptyStringsFound = true
		} else {
			// Immediate failure
			return false
		}
	}
	return nonEmptyStringsFound
}

func ExtractFirstAttr(content, name string) (string, bool) {
	// return extract_some($content, "${attr} *-> *`(.*?)`");
	return ExtractFirst(content, name+" *-> *`(.*?)`")
}

func ExtractFirst(content, regex string) (string, bool) {
	params := ExtractAll(content, regex)
	if len(params) > 0 && len(params[0]) > 1 {
		return params[0][1], true
	}

	return "", false
}

func ExtractAll(content, regex string) [][]string {
	stateRegex := regexp.MustCompile(regex)
	// params := stateRegex.FindStringSubmatch(content)
	params := stateRegex.FindAllStringSubmatch(content, -1)

	return params
}

func TrimHtmlText(src string) string {
	//将HTML标签全转换成小写
	re, _ := regexp.Compile("\\<[\\S\\s]+?\\>")
	src = re.ReplaceAllStringFunc(src, strings.ToLower)
	//去除STYLE
	re, _ = regexp.Compile("\\<style[\\S\\s]+?\\</style\\>")
	src = re.ReplaceAllString(src, "")
	//去除SCRIPT
	re, _ = regexp.Compile("\\<script[\\S\\s]+?\\</script\\>")
	src = re.ReplaceAllString(src, "")
	//去除所有尖括号内的HTML代码，并换成换行符
	re, _ = regexp.Compile("\\<[\\S\\s]+?\\>")
	src = re.ReplaceAllString(src, "\n")
	//去除连续的换行符
	re, _ = regexp.Compile("\\s{2,}")
	src = re.ReplaceAllString(src, "\n")
	return strings.TrimSpace(src)
}

// 过滤不符合utf8规则的字符，用replaceStr字符串替换
func TrimInvalidUTF8Bytes(s string, replaceStr string) string {
	var dst []byte
	bts := []byte(s)
	index := 0
	replaceBytes := []byte(replaceStr)
	for len(s) > 0 {
		r, size := utf8.DecodeRuneInString(s)
		if r == utf8.RuneError && size == 1 {
			dst = append(dst, replaceBytes...)
		} else {
			dst = append(dst, bts[index:index+size]...)
		}
		index += size
		s = s[size:]
	}

	return string(dst)
}

func Crc32(content string) uint32 {
	return crc32.ChecksumIEEE([]byte(content))
}

func Md5(str string) string {
	return fmt.Sprintf("%x", md5.Sum([]byte(str)))
}

func Sha256(bts []byte) string {
	hash := sha256.New()
	hash.Write(bts)
	return fmt.Sprintf("%064X", hash.Sum(nil))
}

func Decrypt(iv string, txt string) (string, error) {
	return CbcDecrypt("bangbangbangbang", iv, txt)
}

func Encrypt(iv string, txt string) (string, error) {
	return CbcEncrypt("bangbangbangbang", iv, txt)
}

func Pkcs5UnPadding(origData []byte) []byte {
	length := len(origData)
	unpadding := int(origData[length-1])
	return origData[:(length - unpadding)]
}

func AesDecryptCBC(encrypted []byte, key []byte) (decrypted []byte, err error) {
	block, err := aes.NewCipher(key) // 分组秘钥
	if err != nil {
		return
	}
	blockSize := block.BlockSize()                              // 获取秘钥块的长度
	blockMode := cipher.NewCBCDecrypter(block, key[:blockSize]) // 加密模式
	decrypted = make([]byte, len(encrypted))                    // 创建数组
	blockMode.CryptBlocks(decrypted, encrypted)                 // 解密
	decrypted = Pkcs5UnPadding(decrypted)                       // 去除补全码
	return
}

func CbcEncrypt(key string, iv string, txt string) (string, error) {
	plaintext := []byte(txt)

	n := aes.BlockSize - (len(plaintext) % aes.BlockSize)
	if n != aes.BlockSize {
		for i := 0; i < n; i++ {
			plaintext = append(plaintext, ' ')
		}
	}

	block, err := aes.NewCipher([]byte(key))
	if err != nil {
		panic(err)
	}

	// ciphertext := make([]byte, aes.BlockSize)
	ciphertext := make([]byte, len(plaintext))

	mode := cipher.NewCBCEncrypter(block, []byte(iv))
	mode.CryptBlocks(ciphertext, plaintext)

	return fmt.Sprintf("%x", ciphertext), nil
}

func CbcDecrypt(key string, iv string, txt string) (string, error) {
	ciphertext, _ := hex.DecodeString(txt)

	block, err := aes.NewCipher([]byte(key))
	if err != nil {
		return "", fmt.Errorf("init aes erro, %v", err)
	}

	// The IV needs to be unique, but not secure. Therefore it's common to
	// include it at the beginning of the ciphertext.
	if len(ciphertext) < aes.BlockSize {
		return "", fmt.Errorf("ciphertext too short")
	}

	// CBC mode always works in whole blocks.
	if len(ciphertext)%aes.BlockSize != 0 {
		return "", fmt.Errorf("ciphertext is not a multiple of the block size")
	}

	mode := cipher.NewCBCDecrypter(block, []byte(iv))

	mode.CryptBlocks(ciphertext, ciphertext)

	n := len(ciphertext)
	padLen := 0

	if n > 0 {
		for i := n - 1; i > 0; i-- {
			if ciphertext[i] != ' ' && ciphertext[i] != 0 {
				break
			}

			padLen++
		}
	}

	return string(ciphertext[:n-padLen]), nil
}

func ReplaceRegex(source, regex, replace string) string {
	var re = regexp.MustCompile(regex)
	s := re.ReplaceAllString(source, replace)
	return s
}

func EachCsvFile(filePath string, fun func(index int, row []string) bool) error {
	if !FileExists(filePath) {
		return fmt.Errorf("can't find csv file -> `%s`", filePath)
	}

	f, _ := os.Open(filePath)
	defer f.Close()

	r := csv.NewReader(f)

	index := 0
	for {
		record, err := r.Read()
		if err == io.EOF {
			break
		}

		if err != nil {
			return err
		}

		index++

		if !fun(index, record) {
			break
		}
	}

	return nil
}

func WriteCsvFile(filePath string, record []string) error {
	f, _ := os.Open(filePath)
	defer f.Close()

	w := csv.NewWriter(f)
	err := w.Write(record)

	w.Flush()
	return err
}

func GetTmpFilePath(key string) (fileKey string, filePath string) {
	fileKey = fmt.Sprintf("%s_%s", key, strings.Replace(time.Now().Format("20060102150405.000000"), ".", "_", 1))
	filePath = fmt.Sprintf("/tmp/%s", fileKey)
	return
}

func UrlParse(str string) (string, error) {
	u, err := url.Parse(str)
	if err != nil {
		return "", err
	}
	return u.String(), nil
}

//func ToString(val interface{}) string {
//	return fmt.Sprintf("%v", val)
//}

type argInt []int

// get int by index from int slice
func (a argInt) Get(i int, args ...int) (r int) {
	if i >= 0 && i < len(a) {
		r = a[i]
	}
	if len(args) > 0 {
		r = args[0]
	}
	return
}

// ToStr interface to string
func ToString(value interface{}, args ...int) (s string) {
	if reflect.TypeOf(value).Kind() == reflect.Ptr {
		if e := reflect.ValueOf(value).Elem(); e.IsValid() {
			return ToString(e.Interface(), args...)
		}
	}

	switch v := value.(type) {
	case bool:
		s = strconv.FormatBool(v)
	case float32:
		s = strconv.FormatFloat(float64(v), 'f', argInt(args).Get(0, -1), argInt(args).Get(1, 32))
	case float64:
		s = strconv.FormatFloat(v, 'f', argInt(args).Get(0, -1), argInt(args).Get(1, 64))
	case int:
		s = strconv.FormatInt(int64(v), argInt(args).Get(0, 10))
	case int8:
		s = strconv.FormatInt(int64(v), argInt(args).Get(0, 10))
	case int16:
		s = strconv.FormatInt(int64(v), argInt(args).Get(0, 10))
	case int32:
		s = strconv.FormatInt(int64(v), argInt(args).Get(0, 10))
	case int64:
		s = strconv.FormatInt(v, argInt(args).Get(0, 10))
	case uint:
		s = strconv.FormatUint(uint64(v), argInt(args).Get(0, 10))
	case uint8:
		s = strconv.FormatUint(uint64(v), argInt(args).Get(0, 10))
	case uint16:
		s = strconv.FormatUint(uint64(v), argInt(args).Get(0, 10))
	case uint32:
		s = strconv.FormatUint(uint64(v), argInt(args).Get(0, 10))
	case uint64:
		s = strconv.FormatUint(v, argInt(args).Get(0, 10))
	case string:
		s = v
	case []byte:
		s = string(v)
	default:
		s = fmt.Sprintf("%v", v)
	}
	return s
}

// ToInt64 interface to int64
func ToInt64(value interface{}) (d int64, err error) {
	val := reflect.ValueOf(value)
	switch value.(type) {
	case int, int8, int16, int32, int64:
		d = val.Int()
	case uint, uint8, uint16, uint32, uint64:
		d = int64(val.Uint())
	case string:
		d, err = strconv.ParseInt(val.String(), 10, 64)
	case float32, float64:
		d = int64(val.Float())
	default:
		err = fmt.Errorf("ToInt64 need numeric not `%T`", value)
	}
	return
}

func ToFoloat32(value string) (d float32, err error) {
	floatTmp, err := strconv.ParseFloat(value, 32)
	d = float32(floatTmp)
	return
}

func ToFoloat64(value string) (d float64, err error) {
	floatTmp, err := strconv.ParseFloat(value, 64)
	d = float64(floatTmp)
	return
}

func Decimal(value float64, size int) float64 {
	value, _ = strconv.ParseFloat(fmt.Sprintf("%."+ToString(size)+"f", value), 64)
	return value
}

//func ToInt64(val string) (i int64, err error) {
//	return strconv.ParseInt(val, 10, 64)
//}

func Round(val float64, precision int) float64 {
	p := math.Pow10(precision)
	return math.Floor(val*p+0.5) / p
}

func ToJson(val interface{}) (jsonResult string, err error) {
	var jsonOut []byte
	jsonOut, err = json.Marshal(val)
	jsonResult = string(jsonOut)
	return
}

func ToJsonMust(val interface{}) (jsonResult string) {
	var jsonOut []byte
	jsonOut, _ = json.Marshal(val)
	jsonResult = string(jsonOut)
	return
}

func FromJson(data []byte, v interface{}) error {
	return json.Unmarshal(data, v)
}

func ObjectToObject(fromObject interface{}, toObject interface{}) error {
	if jsonResult, err := ToJson(fromObject); err != nil {
		return err
	} else {
		return json.Unmarshal([]byte(jsonResult), toObject)
	}
}

func EachLineString(text string, cb func(string) bool) {
	EachLine(strings.NewReader(text), cb)
}

func EachLinePath(path string, cb func(string) bool) {
	if !FileExists(path) {
		logrus.Error("file not exists -> ", path)
		return
	}

	file, _ := os.Open(path)
	EachLine(file, cb)
}

func EachLine(reader io.Reader, cb func(string) bool) {
	scanner := bufio.NewScanner(reader)
	for scanner.Scan() {
		if !cb(scanner.Text()) {
			break
		}
	}
}

var re = regexp.MustCompile("(_|-)([a-zA-Z]+)")

func ToCamelCase(str string) string {
	camel := re.ReplaceAllString(str, " $2")
	camel = strings.Title(camel)
	camel = strings.Replace(camel, " ", "", -1)

	return camel
}

var snowflakeNode, _ = snowflake.NewNode(1)

func Guid() int64 {
	// Generate a snowflake ID.
	id := snowflakeNode.Generate().Int64()

	return id
}

// Returns full path; ~ replaced with actual home directory
func ExpandedFilename(filename string) string {
	if filename == "" {
		panic("filename was empty")
	}

	if len(filename) > 2 && filename[:2] == "~/" {
		if usr, err := user.Current(); err == nil {
			filename = filepath.Join(usr.HomeDir, filename[2:])
		}
	}

	result, err := filepath.Abs(filename)

	if err != nil {
		panic(err)
	}

	return result
}

// https://github.com/mgutz/str/blob/master/funcsPZ.go
// ToArgv converts string s into an argv for exec.
func ToArgv(s string) []string {
	const (
		InArg = iota
		InArgQuote
		OutOfArg
	)
	currentState := OutOfArg
	currentQuoteChar := "\x00" // to distinguish between ' and " quotations
	// this allows to use "foo'bar"
	currentArg := ""
	argv := []string{}

	isQuote := func(c string) bool {
		return c == `"` || c == `'`
	}

	isEscape := func(c string) bool {
		return c == `\`
	}

	isWhitespace := func(c string) bool {
		return c == " " || c == "\t"
	}

	L := len(s)
	for i := 0; i < L; i++ {
		c := s[i : i+1]

		//fmt.Printf("c %s state %v arg %s argv %v i %d\n", c, currentState, currentArg, args, i)
		if isQuote(c) {
			switch currentState {
			case OutOfArg:
				currentArg = ""
				fallthrough
			case InArg:
				currentState = InArgQuote
				currentQuoteChar = c

			case InArgQuote:
				if c == currentQuoteChar {
					currentState = InArg
				} else {
					currentArg += c
				}
			}

		} else if isWhitespace(c) {
			switch currentState {
			case InArg:
				argv = append(argv, currentArg)
				currentState = OutOfArg
			case InArgQuote:
				currentArg += c
			case OutOfArg:
				// nothing
			}

		} else if isEscape(c) {
			switch currentState {
			case OutOfArg:
				currentArg = ""
				currentState = InArg
				fallthrough
			case InArg:
				fallthrough
			case InArgQuote:
				if i == L-1 {
					if runtime.GOOS == "windows" {
						// just add \ to end for windows
						currentArg += c
					} else {
						panic("Escape character at end string")
					}
				} else {
					if runtime.GOOS == "windows" {
						peek := s[i+1 : i+2]
						if peek != `"` {
							currentArg += c
						}
					} else {
						i++
						c = s[i : i+1]
						currentArg += c
					}
				}
			}
		} else {
			switch currentState {
			case InArg, InArgQuote:
				currentArg += c

			case OutOfArg:
				currentArg = ""
				currentArg += c
				currentState = InArg
			}
		}
	}

	if currentState == InArg {
		argv = append(argv, currentArg)
	} else if currentState == InArgQuote {
		panic("Starting quote has no ending quote.")
	}

	return argv
}

func EachCsvReader(reader io.Reader, fun func(index int, row []string) bool, initFun func(reader *csv.Reader)) error {
	r := csv.NewReader(reader)
	if initFun != nil {
		initFun(r)
	}

	index := 0
	for {
		record, err := r.Read()
		if err == io.EOF {
			break
		}

		if err != nil {
			return err
		}

		index++

		if !fun(index, record) {
			break
		}
	}

	return nil
}

func LineCounterPath(path string) (int, error) {
	if !FileExists(path) {
		return 0, fmt.Errorf("file not exists -> %s", path)
	}

	file, _ := os.Open(path)
	return LineCounter(file)
}

func LineCounter(r io.Reader) (int, error) {
	buf := make([]byte, 32*1024)
	count := 0
	lineSep := []byte{'\n'}

	for {
		c, err := r.Read(buf)
		count += bytes.Count(buf[:c], lineSep)

		switch {
		case err == io.EOF:
			return count, nil

		case err != nil:
			return count, err
		}
	}
}

var isLocalOpt int8 = -1

func SetLocal(val bool) {
	if val {
		isLocalOpt = 1
	} else {
		isLocalOpt = 0
	}
}

func IsLocal() bool {
	if isLocalOpt == -1 {
		if FileExists("/var/local.st") {
			isLocalOpt = 1
		} else {
			isLocalOpt = 0
		}
	}

	return isLocalOpt == 1
}

func EachField(item interface{}, fn func(typeField reflect.StructField, valField reflect.Value) error) error {
	t := reflect.TypeOf(item)
	v := reflect.ValueOf(item)
	for i := 0; i < t.Elem().NumField(); i++ {
		err := fn(t.Elem().Field(i), v.Elem().Field(i))
		if err != nil {
			return err
		}
	}

	return nil
}

// StrTo is the target string
type StrTo string

// Set string
func (f *StrTo) Set(v string) {
	if v != "" {
		*f = StrTo(v)
	} else {
		f.Clear()
	}
}

// Clear string
func (f *StrTo) Clear() {
	var c rune = 0x1E
	*f = StrTo(c)
}

// Exist check string exist
func (f StrTo) Exist() bool {
	var c rune = 0x1E
	return string(f) != string(c)
}

// Bool string to bool
func (f StrTo) Bool() (bool, error) {
	return strconv.ParseBool(f.String())
}

// Float32 string to float32
func (f StrTo) Float32() (float32, error) {
	v, err := strconv.ParseFloat(f.String(), 32)
	return float32(v), err
}

// Float64 string to float64
func (f StrTo) Float64() (float64, error) {
	return strconv.ParseFloat(f.String(), 64)
}

// Int string to int
func (f StrTo) Int() (int, error) {
	v, err := strconv.ParseInt(f.String(), 10, 32)
	return int(v), err
}

// Int8 string to int8
func (f StrTo) Int8() (int8, error) {
	v, err := strconv.ParseInt(f.String(), 10, 8)
	return int8(v), err
}

// Int16 string to int16
func (f StrTo) Int16() (int16, error) {
	v, err := strconv.ParseInt(f.String(), 10, 16)
	return int16(v), err
}

// Int32 string to int32
func (f StrTo) Int32() (int32, error) {
	v, err := strconv.ParseInt(f.String(), 10, 32)
	return int32(v), err
}

// Int64 string to int64
func (f StrTo) Int64() (int64, error) {
	v, err := strconv.ParseInt(f.String(), 10, 64)
	if err != nil {
		i := new(big.Int)
		ni, ok := i.SetString(f.String(), 10) // octal
		if !ok {
			return v, err
		}
		return ni.Int64(), nil
	}
	return v, err
}

// Uint string to uint
func (f StrTo) Uint() (uint, error) {
	v, err := strconv.ParseUint(f.String(), 10, 32)
	return uint(v), err
}

// Uint8 string to uint8
func (f StrTo) Uint8() (uint8, error) {
	v, err := strconv.ParseUint(f.String(), 10, 8)
	return uint8(v), err
}

// Uint16 string to uint16
func (f StrTo) Uint16() (uint16, error) {
	v, err := strconv.ParseUint(f.String(), 10, 16)
	return uint16(v), err
}

// Uint32 string to uint31
func (f StrTo) Uint32() (uint32, error) {
	v, err := strconv.ParseUint(f.String(), 10, 32)
	return uint32(v), err
}

// Uint64 string to uint64
func (f StrTo) Uint64() (uint64, error) {
	v, err := strconv.ParseUint(f.String(), 10, 64)
	if err != nil {
		i := new(big.Int)
		ni, ok := i.SetString(f.String(), 10)
		if !ok {
			return v, err
		}
		return ni.Uint64(), nil
	}
	return v, err
}

// String string to string
func (f StrTo) String() string {
	if f.Exist() {
		return string(f)
	}
	return ""
}

// set field value to row container
func SetFieldValue(ind reflect.Value, value interface{}) {
	switch ind.Kind() {
	case reflect.Bool:
		if value == nil {
			ind.SetBool(false)
		} else if v, ok := value.(bool); ok {
			ind.SetBool(v)
		} else {
			v, _ := StrTo(ToString(value)).Bool()
			ind.SetBool(v)
		}

	case reflect.String:
		if value == nil {
			ind.SetString("")
		} else {
			ind.SetString(ToString(value))
		}

	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		if value == nil {
			ind.SetInt(0)
		} else {
			val := reflect.ValueOf(value)
			switch val.Kind() {
			case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
				ind.SetInt(val.Int())
			case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
				ind.SetInt(int64(val.Uint()))
			default:
				v, _ := StrTo(ToString(value)).Int64()
				ind.SetInt(v)
			}
		}
	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
		if value == nil {
			ind.SetUint(0)
		} else {
			val := reflect.ValueOf(value)
			switch val.Kind() {
			case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
				ind.SetUint(uint64(val.Int()))
			case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
				ind.SetUint(val.Uint())
			default:
				v, _ := StrTo(ToString(value)).Uint64()
				ind.SetUint(v)
			}
		}
	case reflect.Float64, reflect.Float32:
		if value == nil {
			ind.SetFloat(0)
		} else {
			val := reflect.ValueOf(value)
			switch val.Kind() {
			case reflect.Float64:
				ind.SetFloat(val.Float())
			default:
				v, _ := StrTo(ToString(value)).Float64()
				ind.SetFloat(v)
			}
		}

	case reflect.Struct:
		if value == nil {
			ind.Set(reflect.Zero(ind.Type()))

		} else if _, ok := ind.Interface().(time.Time); ok {
			var str string
			switch d := value.(type) {
			case time.Time:
				//o.orm.alias.DbBaser.TimeFromDB(&d, o.orm.alias.TZ)
				ind.Set(reflect.ValueOf(d))
			case []byte:
				str = string(d)
			case string:
				str = d
			}
			if str != "" {
				if len(str) >= 19 {
					str = str[:19]
					t, err := time.ParseInLocation(formatDateTime, str, time.UTC)
					if err == nil {
						t = t.In(DefaultTimeLoc)
						ind.Set(reflect.ValueOf(t))
					}
				} else if len(str) >= 10 {
					str = str[:10]
					t, err := time.ParseInLocation(formatDate, str, DefaultTimeLoc)
					if err == nil {
						ind.Set(reflect.ValueOf(t))
					}
				}
			}
		}
	}
}

func GetHeaderContentLength(urlPath string) (int64, error) {
	if headers, err := GetHeader(urlPath); err != nil {
		return 0, err
	} else {
		contentLength := headers.Get("Content-Length")
		cLength, _ := ToInt64(contentLength)
		return cLength, err
	}
}

func GetHeaderEtag(urlPath string) (string, error) {
	if headers, err := GetHeader(urlPath); err != nil {
		return "", err
	} else {
		etag := headers.Get("Etag")
		if etag != "" {
			etag = strings.Trim(etag, `"`)
		}
		return etag, err
	}
}

func GetHeader(urlPath string) (http.Header, error) {
	if resp, err := req.Get(urlPath); err != nil {
		return nil, err
	} else {
		return resp.Response().Header, nil
	}
}

func GetMemoryUsage() string {
	var m runtime.MemStats
	runtime.ReadMemStats(&m)
	// For info on each, see: https://golang.org/pkg/runtime/#MemStats
	return fmt.Sprintf("Alloc = %v MiB\tTotalAlloc = %v MiB\tSys = %v MiB\tNumGC = %v\n",
		byteToMb(m.Alloc),
		byteToMb(m.TotalAlloc),
		byteToMb(m.Sys),
		m.NumGC,
	)
}

func byteToMb(byte uint64) uint64 {
	return byte / 1024 / 1024
}

func ParseDate(dateTime string) time.Time {
	dt, _ := time.Parse("2006-01-02 15:04:05", dateTime)
	return dt
}

func FormatDate(dt time.Time) string {
	return dt.Format("2006-01-02 15:04:05")
}

func MinTimestamp() time.Time {
	return ParseDate("1981-11-11 11:11:11")
}

func JwtEncode(claims jwt.Claims, key string) (string, error) {
	if claims == nil {
		return "", nil
	}

	if key == "" {
		key = string(jwt.UnsafeAllowNoneSignatureType)
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	return token.SignedString([]byte(key))
}

func JwtDecode(encodeStr string, key string) (jwt.MapClaims, error) {
	if encodeStr == "" {
		return make(jwt.MapClaims), nil
	}

	if key == "" {
		key = string(jwt.UnsafeAllowNoneSignatureType)
	}

	token, err := jwt.Parse(encodeStr, func(token *jwt.Token) (interface{}, error) {
		return []byte(key), nil
	})

	if token == nil {
		return jwt.MapClaims{}, nil
	}

	if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
		return claims, nil
	}

	return nil, err
}

func JwtEncodeString(code string, key interface{}) (string, error) {
	if key == nil {
		key = []byte(jwt.UnsafeAllowNoneSignatureType)
	}

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
		"__key": code,
	})
	return token.SignedString(key)
}

func JwtDecodeString(encodeStr string, key interface{}) (string, error) {
	if key == nil {
		key = []byte(jwt.UnsafeAllowNoneSignatureType)
	}

	token, err := jwt.Parse(encodeStr, func(token *jwt.Token) (interface{}, error) {
		return key, nil
	})

	if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
		if val, ok := claims["__key"]; ok {
			return ToString(val), nil
		} else {
			marshal, err := json.Marshal(claims)
			return string(marshal), err
		}
	}

	return "", err
}

func ToDateRfc3339(tStr string) (time.Time, error) {
	return time.ParseInLocation(time.RFC3339, tStr, time.UTC)
}

func ToCstDateRfc3339(tStr string, fromLocation ...string) (time.Time, error) {
	fLocation := "UTC"
	if len(fromLocation) > 0 {
		fLocation = fromLocation[0]
	}

	if fromTime, err := time.ParseInLocation(time.RFC3339, tStr, time.UTC); err != nil {
		return time.Time{}, err
	} else {
		local, _ := time.LoadLocation(fLocation)
		_, offset := fromTime.In(local).Zone()
		if tLocal, err := time.LoadLocation("Asia/Shanghai"); err != nil {
			return time.Time{}, err
		} else {
			return fromTime.In(tLocal).Add(time.Second * time.Duration(-offset)), nil
		}
	}
}

func ToCstDate(t time.Time) (time.Time, error) {
	if t.IsZero() {
		return t, nil
	}

	if local, err := time.LoadLocation("Asia/Shanghai"); err != nil {
		return t, err
	} else {
		return t.In(local), nil
	}
}

// 结构体转map[string]string 以tag.json为key
func StructToMap(i interface{}) (map[string]string, error) {
	if reflect.TypeOf(i).Kind() != reflect.Struct {
		return nil, errors.New("it's not a struct type")
	}

	data := make(map[string]string)
	t := reflect.TypeOf(i)
	v := reflect.ValueOf(i)
	for j := 0; j < t.NumField(); j++ {
		jsonTag := t.Field(j).Tag.Get("json")
		if jsonTag == "" {
			continue
		}
		data[jsonTag] = fmt.Sprintf("%v", v.Field(j).Interface())
	}

	return data, nil
}

// 结构体转map[string]interface{} 以tag.json为key
func StructToMapInterface(obj interface{}) (map[string]interface{}, error) {
	if reflect.TypeOf(obj).Kind() != reflect.Struct {
		return nil, errors.New("it's not a struct type")
	}

	t := reflect.TypeOf(obj)
	v := reflect.ValueOf(obj)

	var data = make(map[string]interface{})
	for i := 0; i < t.NumField(); i++ {
		tagValue := t.Field(i).Tag.Get("json")
		data[tagValue] = v.Field(i).Interface()
	}
	return data, nil
}

func CollectHeader(filename string) (header *http.Header) {
	header = &http.Header{}
	suffix, _ := ExtractFirst(filename, `\.([^\.]+)$`)
	suffix = strings.ToLower(suffix)
	c := "Content-Type"
	switch suffix {
	case "jpg", "png", "jpeg", "gif", "bmp", "swf", "tif", "cdr", "dxf", "ufo", "ai":
		header.Set(c, fmt.Sprintf("image/%s", suffix))
	case "svg":
		header.Set(c, "xml/svg")
	case "zip":
		header.Set(c, "application/x-zip-compressed")
	case "rar", "pdf", "psd":
		header.Set(c, "application/octet-stream")
	case "html", "htm":
		header.Set(c, "text/html;charset=UTF-8")
	case "xml":
		header.Set(c, "text/xml;charset=UTF-8")
	case "js", "json":
		header.Set(c, "application/json;charset=UTF-8")
	default:
		header.Set(c, "application/octet-stream")
	}
	header.Set("Content-Disposition", fmt.Sprintf("attachment;filename=\"%s\"", strings.ReplaceAll(filename, " ", "")))

	return
}

func GetHttpProxyClient(httpProxy string, timeout time.Duration) *http.Client {
	netTransPort := &http.Transport{
		Dial: func(netw, addr string) (net.Conn, error) {
			conn, err := net.DialTimeout(netw, addr, time.Second*timeout) //设置建立连接超时
			if err != nil {
				return nil, err
			}
			conn.SetDeadline(time.Now().Add(time.Second * timeout)) //设置发送接受数据超时
			return conn, nil
		},
		ResponseHeaderTimeout: time.Second * timeout,
	}
	if len(httpProxy) > 0 {
		p, err := url.Parse(httpProxy)
		if err == nil {
			netTransPort.Proxy = http.ProxyURL(p)
		}
	}

	client := &http.Client{
		Timeout:   timeout * time.Second,
		Transport: netTransPort,
	}

	return client
}

// 获取IP归属地
func IPLocation(ip string) string {
	resp, err := http.Get(fmt.Sprintf("https://sp0.baidu.com/8aQDcjqpAAV3otqbppnN2DJv/api.php?query=%s&co=&resource_id=5809&t=1602731715853&ie=utf8&oe=gbk&cb=op_aladdin_callback&format=json&tn=baidu&cb=jQuery1102023194267462737472_1602731705643&_=1602731705644", ip))
	if err != nil {
		return "未知"
	}
	defer resp.Body.Close()

	type Result struct {
		ResultCode string `json:"ResultCode"`
		Result     []struct {
			DisplayData struct {
				ResultData struct {
					TplData struct {
						Location string `json:"location"`
					} `json:"tplData"`
				} `json:"resultData"`
			} `json:"DisplayData"`
		} `json:"Result"`
	}

	bodyByte, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "未知"
	}

	res := new(Result)
	js, _ := ExtractFirst(string(bodyByte), `^jQuery.*?\(({.*})\)$`)
	if err = json.Unmarshal([]byte(js), res); err != nil {
		return "未知"
	}
	if res.ResultCode == "0" && len(res.Result) > 0 {
		return res.Result[0].DisplayData.ResultData.TplData.Location
	}

	return "未知"
}

// 读取xlsx文件
// 格式必须满足：
// 1. 有两行表头,第一行为字段名称，第二行为字段的唯一key
// 2. 第二行表头为key ，该key和itemList中字段的tag.json一致
func ReadXlsx(r io.Reader, slicePtr interface{}) (rows int, err error) {
	fileBin, err := ioutil.ReadAll(r)
	if err != nil {
		return
	}
	file, err := xlsx.OpenBinary(fileBin)
	if err != nil {
		return
	}
	if len(file.Sheets) == 0 {
		err = fmt.Errorf("there is no sheet")
		return
	}
	sheet := file.Sheets[0]
	if len(sheet.Rows) < 2 {
		err = fmt.Errorf("format error, title not found")
		return
	}

	sliceValue := reflect.Indirect(reflect.ValueOf(slicePtr))
	if sliceValue.Kind() != reflect.Slice {
		err = errors.New("needs a pointer to a slice")
		return
	}
	sliceElementType := sliceValue.Type().Elem()
	isPtr := false
	if sliceElementType.Kind() == reflect.Ptr {
		sliceElementType = sliceElementType.Elem()
		isPtr = true
	}
	if sliceElementType.Kind() != reflect.Struct {
		err = errors.New("element need a struct")
		return
	}
	fieldIndex := make(map[string]int)
	for i := 0; i < sliceElementType.NumField(); i++ {
		tag := sliceElementType.Field(i).Tag.Get("json")
		fieldIndex[tag] = i
	}
	p := reflect.Indirect(reflect.ValueOf(slicePtr))

	rows = len(sheet.Rows) - 2
	for i := 2; i < len(sheet.Rows); i++ {
		item := reflect.New(sliceElementType).Elem()
		for j, cell := range sheet.Rows[i].Cells {
			if j >= len(sheet.Rows[1].Cells) {
				break
			}
			fieldName := sheet.Rows[1].Cells[j].String()
			index, ok := fieldIndex[fieldName]
			if !ok {
				continue
			}
			tp := sliceElementType.Field(index).Type
			vv := StrTo(cell.String())
			switch tp.Kind() {
			case reflect.String:
				item.Field(index).SetString(cell.String())
			case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint64:
				v, err := vv.Int64()
				if err != nil {
					continue
				}
				item.Field(index).SetInt(v)
			case reflect.Float32, reflect.Float64:
				v, err := vv.Float64()
				if err != nil {
					continue
				}
				item.Field(index).SetFloat(v)
			case reflect.Bool:
				v, err := vv.Bool()
				if err != nil {
					continue
				}
				item.Field(index).SetBool(v)
			}
		}
		if isPtr {
			p.Set(reflect.Append(p, item.Addr()))
		} else {
			p.Set(reflect.Append(p, item))
		}
	}
	return
}

// 判断字符串切片中是否存在指定的字符串
func IsStrIn(str string, arr []string) bool {
	for _, item := range arr {
		if item == str {
			return true
		}
	}
	return false
}

func IsIntIn(dst int, arr []int) bool {
	for _, item := range arr {
		if dst == item {
			return true
		}
	}
	return false
}

// 求int64列表交集
func Int64MixSet(arr1, arr2 []int64) []int64 {
	var arr []int64
	for _, id := range arr1 {
		if IsInt64In(id, arr2) {
			arr = append(arr, id)
		}
	}
	return arr
}

func IsInt64In(dst int64, arr []int64) bool {
	for _, item := range arr {
		if dst == item {
			return true
		}
	}
	return false
}

// 判断两列表是否有交集
func IsInt64Mixed(arr1, arr2 []int64) bool {
	for _, a := range arr1 {
		for _, b := range arr2 {
			if a == b {
				return true
			}
		}
	}
	return false
}

func HttpProxyClient(proxy string, timeouts ...time.Duration) *http.Client {
	var timeout time.Duration = 60
	if len(timeouts) > 0 {
		timeout = timeouts[0]
	}
	netTransPort := &http.Transport{
		Dial: func(netw, addr string) (net.Conn, error) {
			conn, err := net.DialTimeout(netw, addr, time.Second*timeout) //设置建立连接超时
			if err != nil {
				return nil, err
			}
			conn.SetDeadline(time.Now().Add(time.Second * timeout)) //设置发送接受数据超时
			return conn, nil
		},
		ResponseHeaderTimeout: time.Second * timeout,
	}
	if proxy != "" {
		p, err := url.Parse(proxy)
		if err == nil {
			netTransPort.Proxy = http.ProxyURL(p)
		}
	}

	client := &http.Client{
		Timeout:   timeout * time.Second,
		Transport: netTransPort,
	}

	return client
}

func GetOriginHost(c *gin.Context) string {
	host := c.GetHeader("X-Forwarded-Host")
	if host != "" {
		return host
	} else {
		return c.Request.Host
	}
}

type AsyncFunc struct {
	wg             sync.WaitGroup
	isLimitProcess bool
	processLock    chan struct{}
}

// 通过chan长度控制最大开启的goroutine数量
func (this *AsyncFunc) SetMaxProcess(n int) {
	if n <= 0 {
		return
	}
	this.processLock = make(chan struct{}, n)
	this.isLimitProcess = true
}

func (this *AsyncFunc) Wait() {
	this.wg.Wait()
	if this.isLimitProcess {
		close(this.processLock)
	}
}

func (this *AsyncFunc) Add(f func()) {
	if f == nil {
		return
	}

	this.wg.Add(1)
	if this.isLimitProcess {
		this.processLock <- struct{}{} // 通过chan长度控制最大开启的goroutine数量
	}
	go func() {
		defer func() {
			if err := recover(); err != nil {
				// 打印调用栈
				logger.ErrorStack("panic: ", err)
			}
			this.wg.Done()
			if this.isLimitProcess {
				<-this.processLock
			}
		}()

		f()
	}()
}

func PreHandleEndDate(date string) string {
	if len(date) == 10 {
		return date + " 23:59:59"
	}
	return date
}

// 图片压缩，仅支持png, jpg, jpeg
func CompressImage(quality int, data []byte) []byte {
	img, layout, err := image.Decode(bytes.NewReader(data))
	if err != nil {
		logger.Error(err)
		return data
	}

	buf := bytes.Buffer{}
	switch layout {
	case "jpeg", "jpg":
		err = jpeg.Encode(&buf, img, &jpeg.Options{Quality: quality})
	case "png":
		newImg := image.NewRGBA(img.Bounds())
		draw.Draw(newImg, newImg.Bounds(), &image.Uniform{C: color.White}, image.Point{}, draw.Src)
		draw.Draw(newImg, newImg.Bounds(), img, img.Bounds().Min, draw.Over)
		err = jpeg.Encode(&buf, newImg, &jpeg.Options{Quality: quality})
	default:
		return data
	}
	if err != nil {
		logger.Error(err)
		return data
	}
	if buf.Len() > len(data) || buf.Len() == 0 {
		return data
	}
	return buf.Bytes()
}

// 返回值的单位为米
func EarthDistance(lat1, lng1, lat2, lng2 float64) float64 {
	radius := float64(6371000) // 6378137
	rad := math.Pi / 180.0
	lat1 = lat1 * rad
	lng1 = lng1 * rad
	lat2 = lat2 * rad
	lng2 = lng2 * rad
	theta := lng2 - lng1
	dist := math.Acos(math.Sin(lat1)*math.Sin(lat2) + math.Cos(lat1)*math.Cos(lat2)*math.Cos(theta))
	return dist * radius
}

// int64切片join
func Int64Join(arr []int64, sep string) string {
	buff := bytes.Buffer{}
	for i, a := range arr {
		if i == 0 {
			buff.WriteString(fmt.Sprintf("%d", a))
		} else {
			buff.WriteString(fmt.Sprintf("%s%d", sep, a))
		}
	}

	return buff.String()
}

// 传入列编号，生成excel的列代码
// 例如 MakeExcelColumn(1) = "A", MakeExcelColumn(2) = "B", MakeExcelColumn(27) = "AA"
func MakeExcelColumn(col int) string {
	N := 4
	B := make([]int, N)
	M := 0
	B[0] = -1
	for i := 0; i < col; i++ {
		t := 1
		for j := 0; j < N; j++ {
			if t == 0 {
				break
			}

			B[j] += t
			t = 0
			if B[j] >= 26 {
				B[j] = 0
				if j >= M {
					M++
					B[j+1] = 0
				} else {
					t = 1
				}
			}
		}
	}

	buf := ""
	for i := M; i >= 0; i-- {
		buf += string(byte(B[i] + 'A'))
	}
	return buf
}

// 逗号隔开的ID列表拆分成[]int64
func SplitStringIds(str string) []int64 {
	arr := strings.Split(str, ",")
	var ids []int64
	for _, item := range arr {
		id, _ := ToInt64(item)
		if !IsInt64In(id, ids) {
			ids = append(ids, id)
		}
	}
	return ids
}

// R 地球半径，单位米
const R = 6367000

// Distance
// lonA, latA分别为A点的纬度和经度
// lonB, latB分别为B点的纬度和经度
// 返回的距离单位为米
func Distance(lonA, latA, lonB, latB float64) float64 {
	c := math.Sin(latA)*math.Sin(latB)*math.Cos(lonA-lonB) + math.Cos(latA)*math.Cos(latB)
	return R * math.Acos(c) * math.Pi / 180
}

// 二分查找，要求数组有序 比较函数返回值：0 相等，-1 小于，1 大于
// 如果目标有多个，则返回下标最小的那个
// 如果不存在返回-1
func BinarySearch(n int, compare func(int) int) int {
	left, right := 0, n-1
	for left <= right {
		mid := left + (right-left)/2
		if compare(mid) == 0 {
			for mid > 0 && compare(mid-1) == 0 {
				mid--
			}
			return mid
		} else if compare(mid) > 0 {
			left = mid + 1
		} else {
			right = mid - 1
		}
	}
	return -1
}

type IdSet struct {
	idSet map[int64]struct{}
}

func (s *IdSet) IsEmpty() bool {
	return len(s.idSet) == 0
}

func (s *IdSet) Add(ids ...int64) {
	if s.idSet == nil {
		s.idSet = make(map[int64]struct{})
	}
	for _, id := range ids {
		s.idSet[id] = struct{}{}
	}
}
func (s *IdSet) Slice() []int64 {
	var ids = make([]int64, 0, len(s.idSet))
	for id := range s.idSet {
		ids = append(ids, id)
	}
	return ids
}

func (s *IdSet) Clear() {
	s.idSet = nil
}

func MakeListExcel(header []string, nextRow func() []interface{}) (buffer *bytes.Buffer, err error) {
	f := excelize.NewFile()
	sheet := f.GetSheetName(0)
	for i, field := range header {
		f.SetCellStr(sheet, MakeExcelColumn(i+1)+"1", field)
	}
	aroundBorders := []excelize.Border{
		{Type: "top", Color: "000000", Style: 1},
		{Type: "right", Color: "000000", Style: 1},
		{Type: "left", Color: "000000", Style: 1},
		{Type: "bottom", Color: "000000", Style: 1},
	}

	headerStyle, _ := f.NewStyle(&excelize.Style{
		Fill:      excelize.Fill{Type: "pattern", Pattern: 1, Color: []string{"FFA500"}},
		Alignment: &excelize.Alignment{Horizontal: "center", Vertical: "center", WrapText: true},
		Font:      &excelize.Font{Bold: true},
		Border:    aroundBorders,
	})
	f.SetCellStyle(sheet, "A1", fmt.Sprintf("%s1", MakeExcelColumn(len(header))), headerStyle)

	row := 1
	for {
		cols := nextRow()
		if len(cols) == 0 {
			break
		}
		col := 0
		row++
		addCell := func(val interface{}) {
			col++
			coor := fmt.Sprintf("%s%d", MakeExcelColumn(col), row)
			f.SetCellValue(sheet, coor, val)
		}
		for _, col := range cols {
			addCell(col)
		}
	}

	bodyStyle, _ := f.NewStyle(&excelize.Style{
		Alignment: &excelize.Alignment{Horizontal: "center", Vertical: "center"},
		Border:    aroundBorders,
	})
	f.SetCellStyle(sheet, "A2", fmt.Sprintf("%s%d", MakeExcelColumn(len(header)), row), bodyStyle)

	buffer, err = f.WriteToBuffer()
	return
}
